package api

import (
	"encoding/json"
	"os"
	"pan/common/conf"
	"pan/common/exception"
	"pan/common/log"
	"pan/common/net"
	"pan/common/util"
	"strings"
)

type SingleUploadRequest struct {
	Dir      string
	FilePath string
	Owner    string
}

type SingleUploadResponse struct {
	Docid    string
	Rev      string
	Filename string
	Editor   string
	Modified int64
}

type SingleUploadBeginRequest struct {
	Docid     string `json:"docid"`
	Length    int64  `json:"length"`
	Name      string `json:"name"`
	ReqMethod string `json:"reqmethod"`
}

type SingleUploadBeginResponse struct {
	FailRsponse

	AuthRequest []string `json:"authrequest"`
	Rev         string   `json:"rev"`
	Name        string   `json:"name"`
	Docid       string   `json:"docid"`
}

type SingleUploadEndRequest struct {
	Docid string `json:"docid"`
	Rev   string `json:"rev"`
}

type SingleUploadEndResponse struct {
	FailRsponse

	Editor   string `json:"editor"`
	Modified int64  `json:"modified"`
}

func SingleUploadApi(tokenid string, req *SingleUploadRequest) (*SingleUploadResponse, error) {
	if !util.IsExists(req.FilePath) {
		return nil, exception.NewExpectError("not found file")
	}

	upload := SingleUpload{
		Tokenid: tokenid,
		Request: req,
	}
	beginRsp, err := upload.Begin()
	if err != nil {
		return nil, err
	}
	if err = upload.Upload(beginRsp); err != nil {
		return nil, err
	}
	endRsp, err := upload.End(beginRsp)
	if err != nil {
		return nil, err
	}

	rsp := &SingleUploadResponse{
		Docid:    beginRsp.Docid,
		Rev:      beginRsp.Rev,
		Filename: beginRsp.Name,
		Editor:   endRsp.Editor,
		Modified: endRsp.Modified,
	}
	return rsp, nil
}

type SingleUpload struct {
	Tokenid string
	Request *SingleUploadRequest

	filename string
	fileSize int64
}

func (s *SingleUpload) findDirDocid() (string, error) {
	req := &DirRequest{
		Dir:   s.Request.Dir,
		Owner: s.Request.Owner,
		By:    "time",
		Sort:  "desc",
	}
	search := SearchDir{Tokenid: s.Tokenid, Request: req}
	docid, err := search.LastDirDocid()
	if err != nil {
		return "", err
	}
	if docid == "" {
		return "", exception.NewExpectError("not found any dirs")
	}
	return docid, nil
}

func (s *SingleUpload) getFileInfo() error {
	if !util.IsExists(s.Request.FilePath) {
		return exception.NewExpectError("not found local file to upload")
	}
	file, err := os.Stat(s.Request.FilePath)
	if err != nil {
		return err
	}
	s.filename = file.Name()
	s.fileSize = file.Size()
	return nil
}

func (s *SingleUpload) kvToMap(slice []string) map[string]string {
	m := make(map[string]string)
	for _, data := range slice {
		k, v := util.SplitKeyValue(data, ":")
		if k != "" {
			m[k] = v
		}
	}
	return m
}

func (s *SingleUpload) Begin() (*SingleUploadBeginResponse, error) {
	err := s.getFileInfo()
	if err != nil {
		return nil, err
	}

	docid, err := s.findDirDocid()
	if err != nil {
		return nil, err
	}
	url := util.PathJoin(conf.GlobalConfig.Url, conf.GlobalConfig.Paths.BeginUpload)

	req := &SingleUploadBeginRequest{
		Docid:     docid,
		Length:    s.fileSize,
		Name:      s.filename,
		ReqMethod: "POST",
	}

	if log.IsDebugEnable() {
		bytes, _ := json.Marshal(req)
		log.Debug("begin upload post: ", url, ", token:", s.Tokenid, ", body:", string(bytes))
	}

	rsp := &SingleUploadBeginResponse{}
	if err = net.PostWithToken(url, s.Tokenid, req, rsp); err != nil {
		return nil, err
	}

	if log.IsDebugEnable() {
		result, _ := json.Marshal(rsp)
		log.Debug("begin upload response: ", string(result))
	}

	if rsp.Errcode != 0 {
		return nil, exception.NewExpectError("begin upload failed, %s", rsp.FailString())
	}
	return rsp, nil
}

func (s *SingleUpload) Upload(beginResp *SingleUploadBeginResponse) error {
	url := beginResp.AuthRequest[1]
	form := s.kvToMap(beginResp.AuthRequest[2:])

	if log.IsDebugEnable() {
		result, _ := json.Marshal(form)
		log.Debug("upload: ", url, ", form:", string(result), ", filePath:", s.Request.FilePath)
	}

	client := net.NewHttpClient().SetMethod(net.HTTP_POST).SetUrl(url)
	for k, v := range form {
		client.AddForm(k, v)
	}
	rsp, err := client.Upload(s.Request.FilePath)
	if err != nil {
		return err
	}

	if log.IsDebugEnable() {
		result, _ := json.Marshal(rsp)
		log.Debug("upload response: ", string(result))
	}

	if rsp.StatusCode < 200 || rsp.StatusCode > 299 {
		if rsp.Body != nil {
			return exception.NewExpectError("upload failed: %s", strings.TrimSpace(string(rsp.Body)))
		} else {
			return exception.NewExpectError("upload failed: statusCode %d", rsp.StatusCode)
		}
	}
	return nil
}

func (s *SingleUpload) End(beginResp *SingleUploadBeginResponse) (*SingleUploadEndResponse, error) {
	url := util.PathJoin(conf.GlobalConfig.Url, conf.GlobalConfig.Paths.EndUpload)

	req := &SingleUploadEndRequest{
		Docid: beginResp.Docid,
		Rev:   beginResp.Rev,
	}

	if log.IsDebugEnable() {
		bytes, _ := json.Marshal(req)
		log.Debug("end upload post: ", url, ", token:", s.Tokenid, ", body:", string(bytes))
	}

	rsp := &SingleUploadEndResponse{}
	if err := net.PostWithToken(url, s.Tokenid, req, rsp); err != nil {
		return nil, err
	}

	if log.IsDebugEnable() {
		result, _ := json.Marshal(rsp)
		log.Debug("end upload response: ", string(result))
	}

	if rsp.Errcode != 0 {
		return nil, exception.NewExpectError("end upload failed, %s", rsp.FailString())
	}
	return rsp, nil
}
